package com.jxl.coreplay.net
{
	import com.jxl.coreplay.events.VideoEvent;
	
	import flash.events.NetStatusEvent;
	import flash.events.TimerEvent;
	import flash.net.NetConnection;
	import flash.net.NetStream;
	import flash.utils.Timer;
	import flash.utils.getTimer;

	public class CorePlayNetStream extends NetStream
	{
		
		protected static var BUFFER_MINIMUM:Number				= 0.1;
		protected static var BUFFER_MAXIMUM:Number				= 10;
		
		public static const PRIORITY_AUDIO_AND_VIDEO:uint	= 0;
		public static const PRIORITY_VIDEO:uint				= 1;
		public static const PRIORITY_AUDIO:uint 			= 2;
		
		public static const STATE_IDLE:String				= "idle";
		public static const STATE_PLAYING:String			= "playing";
		public static const STATE_PAUSED:String				= "paused";
		public static const STATE_BUFFERING:String			= "buffering";
		
		public static const AUDIO_ACCEPTANCE_TIME:Number	= 30 * 1000; // 30 seconds
		public static const VIDEO_ACCEPTANCE_TIME:Number	= 30 * 1000; // 30 seconds
		
		public static const MAX_IDLE_TIME_BEFORE_END:Number	= 3;
		
		public static const STREAM_TYPE_PROGRESSIVE:uint	= 0;
		public static const STREAM_TYPE_STREAMING:uint		= 1;
		public static const STREAM_TYPE_LIVE:uint			= 2;
		
		public var metadata:Object;
		
		protected var poller:Timer;
		protected var _playLive:Boolean						= false;
		protected var _priority:uint						= 0;
		protected var _videoEnabled:Boolean					= true;
		protected var _audioEnabled:Boolean					= true;
		protected var _streamState:String					= "idle";
		
		// flags
		// used to ensure we've fully stopped a stream
		protected var receivedStop:Boolean 					= false;
		protected var bufferFull:Boolean					= false;
		protected var bufferFullTime:Number;
		protected var lastDisabledTime:Number;
		protected var receivingAudio:Boolean				= true;
		protected var receivingVideo:Boolean				= true;
		protected var progressiveTimeDelta:Number			= 0;
		protected var lastProgressiveTimeDelta:Number		= 0;
		protected var noTimeChangeScore:uint				= 0;
		protected var streamType:uint						= 0;
		
		public function get streamState():String { return _streamState; }
		
		protected function setStreamState(value:String):void
		{
			var oldState:String = _streamState;
			switch(value)
			{
				case STATE_IDLE:		_streamState = value;	break;
				case STATE_PLAYING:		_streamState = value;	break;
				case STATE_PAUSED:		_streamState = value;	break;
				case STATE_BUFFERING:	_streamState = value;	break;
				default:
					throw new Error("Uknown stream state value.  Valid values are idle, playing, paused, and buffering.");
					return;
			}
			var stateEvent:VideoEvent = new VideoEvent(VideoEvent.STATE_CHANGED);
			stateEvent.oldState	= oldState;
			stateEvent.newState = _streamState;
			dispatchEvent(stateEvent);
		}
		
		public function get priority():uint { return _priority; }
		public function set priority(value:uint):void
		{
			if(isNaN(value) || (value != 0 && value != 1 && value != 2))
			{
				throw Error("Invalid Priority Value.  Valid values are 0, 1, and 2.");
				return;
			}
			
			_priority = value;
		}
		
		public function get audioEnabled():Boolean { return _audioEnabled; }
		public function set audioEnabled(value:Boolean):void
		{
			_audioEnabled = value;
			receiveVideo(_audioEnabled);
		}
		
		public function get videoEnabled():Boolean { return _videoEnabled; }
		public function set videoEnabled(value:Boolean):void
		{
			_videoEnabled = value;
			receiveVideo2(_videoEnabled);
		}
		
		public function CorePlayNetStream(connection:NetConnection=null):void
		{
			super(connection);
			
			client 			= this;
			bufferTime 		= BUFFER_MINIMUM;
			
			poller 			= new Timer(1000);
			poller.addEventListener(TimerEvent.TIMER, onTick, false, 0, true);
			
			addEventListener(NetStatusEvent.NET_STATUS, onNetStatus);
		}
		
		protected function onNetStatus(event:NetStatusEvent):void
		{
			switch(event.info.code)
			{
				case "NetStream.Play.StreamNotFound":
					dispatchEvent(new VideoEvent(VideoEvent.STREAM_NOT_FOUND));
					setStreamState(STATE_IDLE);
					break;
					
				case "NetStream.Play.PublishNotify":
				case "NetStream.Play.Start":
					receivedStop 	= false;
					dispatchEvent(new VideoEvent(VideoEvent.VIDEO_STARTED));
					setStreamState(STATE_PLAYING);
					break;
				
				// NOTE: Sometimes on Mac, you won't get a NetStream.Play.Stop, only a
				// NetStream.Buffer.Flush, and then a NetStream.Buffer.Empty.  So, we
				// treat both as a "recevied stop message".
				// furthermore, I don't want a NetStream.Buffer.Flush triggering an idle state,
				// so notice the lack of a "break;" after state = STATE_IDLE.
				case "NetStream.Play.UnpublishNotify":
				case "NetStream.Play.Stop":
					setStreamState(STATE_IDLE);
					
				case "NetStream.Buffer.Flush":
					receivedStop 	= true;
					dispatchEvent(new VideoEvent(VideoEvent.VIDEO_BROADCASTING_STOPPED));
					if(_playLive == true)
					{
						dispatchEvent(new VideoEvent(VideoEvent.VIDEO_STOPPED));
					}
					setStreamState(STATE_IDLE);
					break;
				
				// doesn't fire for progressive
				case "NetStream.Pause.Notify":
					dispatchEvent(new VideoEvent(VideoEvent.VIDEO_PAUSED));
					setStreamState(STATE_PAUSED);
					break;
				
				// doesn't fire for progressive
				case "NetStream.Unpause.Notify":
					dispatchEvent(new VideoEvent(VideoEvent.VIDEO_RESUMED));
					setStreamState(STATE_PLAYING);
					break;
				
				case "NetStream.Buffer.Full":
					bufferFull 						= true;
					bufferFullTime 					= getTimer();
					setStreamState(STATE_PLAYING);
					dispatchEvent(new VideoEvent(VideoEvent.BUFFER_FULL));
					break;
				
				case "NetStream.Seek.Notify":
					dispatchEvent(new VideoEvent(VideoEvent.VIDEO_SEEKED));
					break;
					
				case "NetStream.Buffer.Empty":
					bufferFull 				= false;
					dispatchEvent(new VideoEvent(VideoEvent.BUFFER_EMPTY));
					if(receivedStop == true)
					{
						receivedStop = false;
						if(_playLive == false)
						{
							dispatchEvent(new VideoEvent(VideoEvent.VIDEO_COMPLETED));
						}
					}
					else
					{
						setStreamState(STATE_BUFFERING);
					}
					break;
			}
		}
		
		public function onMetaData(event:Object):void
		{
			metadata = {};
			for(var p:String in event)
			{
				metadata[p] = event[p];
			}
			
			var metadataEvent:VideoEvent 	= new VideoEvent(VideoEvent.METADATA);
			metadataEvent.metadata 			= metadata;
			dispatchEvent(metadataEvent);
		}
		
		protected function onCuePoint(cuePoint:Object):void
		{
			var cuePointEvent:VideoEvent 	= new VideoEvent(VideoEvent.CUE_POINT);
			cuePointEvent.cuePoint 			= cuePoint;
			dispatchEvent(cuePointEvent);
		}
		
		protected function onTick(event:TimerEvent):void
		{
			if(_playLive == true)
			{
				if(liveDelay < .5)
				{
					dispatchEvent(new VideoEvent(VideoEvent.LIVE_DELAY_GOOD));
					var elapsedTime:Number;
					if(_priority == PRIORITY_VIDEO)
					{
						if(receivingAudio == false)
						{
							elapsedTime = getTimer() - lastDisabledTime;
							if(elapsedTime > AUDIO_ACCEPTANCE_TIME)
							{
								receiveAudio(true);
							}
						}
					}
					else if(_priority == PRIORITY_AUDIO)
					{
						if(receivingVideo == false)
						{
							elapsedTime = getTimer() - lastDisabledTime;
							if(elapsedTime > VIDEO_ACCEPTANCE_TIME)
							{
								receiveVideo2(true);
							}
						}
					}
				}
				else if(liveDelay < 1)
				{
					dispatchEvent(new VideoEvent(VideoEvent.LIVE_DELAY_OK));
				}
				else
				{
					if(_priority == PRIORITY_VIDEO)
					{
						if(receivingAudio == true)
						{
							receiveAudio(false);
							lastDisabledTime = getTimer();
						}
					}
					else if(_priority == PRIORITY_AUDIO)
					{
						if(receivingVideo == true)
						{
							receiveVideo2(false);
							lastDisabledTime = getTimer();
						}
					}
					
					dispatchEvent(new VideoEvent(VideoEvent.LIVE_DELAY_BAD));
				}
			}
			else
			{
				// don't mess with the buffer if playback is fixin' to stop
				if(receivedStop == true) return;
				
				// if the buffer is either empty or filling up
				if(bufferFull == false)
				{
					// if the buffer time is the maximum setting
					if(bufferTime == BUFFER_MAXIMUM)
					{
						// set it to the lowest
						bufferTime = BUFFER_MINIMUM;
					}
					// otherwise, we just wait for the buffer to keep filling up
				}
				
				// if the buffer is full
				else
				{
					// and we're set to the minimum buffer size
					if(bufferTime == BUFFER_MINIMUM)
					{
						// NOTE: This ensures that the buffer is not immediately raised upon filling the
						// minimum buffer size.  The Flash Player can refuse to fill the buffer higher than
						// what you set if you don't give it time to "adjust" to the current maximum buffer
						// it has just filled.  This gives it ample time to do so.
						
						
						// and the last time we got a message was a few seconds ago
						var now:Number = getTimer();
						var lastBufferFullTime:Number = now - bufferFullTime;
						if(lastBufferFullTime > (BUFFER_MINIMUM * 2) * 1000)
						{
							// and the buffer actually is nearly full
							if(bufferLength >= bufferTime - .5)
							{
								// then go ahead and up the buffer to the maximum
								bufferTime = BUFFER_MAXIMUM;
							}
						}
					}
				}
				
				// Since Progressive video doesn't have a formal API to know when it's truly stopped;
				// like when a buffer empty happens too quick, or the user scrubs to the end, etc.
				// we use a good ole' poll to ensure we know that mofo's done.
				
				// NOTE: Keep in mind some FLV's do not fire an onMetaData for whatever reason.
				// Others fire it a later times.  Therefore, this algo will not work for those
				// FLV's because it needs the duration to be set.
				if (streamState == STATE_PLAYING)
				{
					if (metadata != null)
					{
						if(metadata.duration > 0)
						{
							progressiveTimeDelta = time - metadata.duration;
							if(progressiveTimeDelta < 0)
							{
								progressiveTimeDelta = -progressiveTimeDelta;
								if(progressiveTimeDelta < 1)
								{
									if(progressiveTimeDelta == lastProgressiveTimeDelta)
									{
										noTimeChangeScore++;
									}
									else
									{
										lastProgressiveTimeDelta = progressiveTimeDelta;
										noTimeChangeScore 			= 0;
									}
									
									if(noTimeChangeScore >= MAX_IDLE_TIME_BEFORE_END)
									{
										lastProgressiveTimeDelta 	= 0;
										noTimeChangeScore 			= 0;
										setStreamState(STATE_IDLE);
										dispatchEvent(new VideoEvent(VideoEvent.VIDEO_COMPLETED));
									}
								}
							}
						}
					}
				}				
			}
		}
		
		override public function set bufferTime(value:Number):void
		{
			var lastVal:Number = bufferTime;
			super.bufferTime = value;
			if(value !== lastVal)
			{
				dispatchEvent(new VideoEvent(VideoEvent.BUFFER_TIME_CHANGE));
			}
		}
		
		public override function receiveAudio(value:Boolean):void
		{
			if(audioEnabled == false) return;
			super.receiveAudio(value);
			receivingAudio = value;
		}
		
		// NOTE: Why the 2?  Well, 9.0.6x (aka MovieStar 9.0.115.0) and above changed the
		// function signature of receiveVideo from Object to *.  This is because
		// while receiveVideo can accept a Number or a Boolean, Number and Boolean
		// no longer extend Object depending on 
		// what version of ECMAScript we're talking about;
		// they are their own primitives, so *
		// was more appopriate.  This will cause the code to act differently
		// read: throw a runtime exception in later versions of the Flash Player
		// (GOOOOOOO backwards compatiability!!!!)
		// dependening on what minor version of Flash Player 9 it runs in.
		// So, while the Lords of ECMAScript can now sleep soundly,
		// those of us in the trenches must use shit like this.
		// This ensures it's it'll work in 9.0.0.0 or 9.0.124.0 and above.
		public function receiveVideo2(value:*):void
		{
			if(videoEnabled == false) return;
			receiveVideo(value);
			if(value is Boolean)
			{
				receivingVideo = value;
			}
			else if(value is Number)
			{
				receivingVideo = true;
			}
		}
		
		public override function play(...arguments):void
		{
			var args:Array = arguments as Array;
			if(args.length == 0)
			{
				throw new Error("NetStreamWrapper::play requires at least 1 argument, typically an FLV file name.");
				return;
			}
			
			if(args.length == 1)
			{
				streamType = STREAM_TYPE_PROGRESSIVE;
			}
			else if(args.length >= 2)
			{
				if(args.length > 1)
				{
					if(args[1] is int)
					{
						var streamLookType:int = args[1] as int;
						if(streamLookType < -2)
						{
							// Flash Player does this coercion anyway
							streamLookType = -2;
							streamType = STREAM_TYPE_LIVE;
						}
						else if(streamLookType == -2)
						{
							streamType = STREAM_TYPE_LIVE;
						}
						else if(streamLookType == -1)
						{
							streamType = STREAM_TYPE_LIVE;
						}
						else if(streamLookType >= 0)
						{
							streamType = STREAM_TYPE_STREAMING;
						}
					}
					else
					{
						throw new Error("NetStream::play's 2nd parameter is supposed to be an int.");
						return;
					}
				}
			}
			super.play.apply(this, arguments);
		}
		
		public override function pause():void
		{
			super.pause();
			pauseVideo();
		}
		
		protected function pauseVideo():void
		{
			setStreamState(STATE_PAUSED);
			if(streamType == STREAM_TYPE_PROGRESSIVE)
			{
				dispatchEvent(new VideoEvent(VideoEvent.VIDEO_PAUSED));
			}
		}
		
		public override function resume():void
		{
			super.resume();
			resumeVideo();
		}
		
		protected function resumeVideo():void
		{
			setStreamState(STATE_PLAYING);
			if(streamType == STREAM_TYPE_PROGRESSIVE)
			{
				dispatchEvent(new VideoEvent(VideoEvent.VIDEO_RESUMED));
			}
		}
		
		public function onPlayStatus(event:Object):void
		{
			if(streamState != STATE_IDLE)
			{
				if(event.code == "NetStream.Play.Complete")
				{
					setStreamState(STATE_IDLE);
					dispatchEvent(new VideoEvent(VideoEvent.VIDEO_COMPLETED));
				}
			}
		}
	}
}